/*
 * Anything: A flexible and easy to use data structure that combines the 
 * characteristics of a list and a hashmap into one data structure.
 * 
 * Based on a paper by Peter Sommerlad and Marcel Rueedi on 
 * 'Do-it-yourself reflection' entered at EuroPLOP 1998.
 * 
 * This library was written completely from scratch and is based only 
 * in spirit on the original Java Anything implementation written by 
 * Andre Weinand/Marcel Rueedi in 1997/99 and enhanced by Stefan Tramm 
 * in 2006.
 * 
 * Copyright (c) 2008 Thomas Marti and others. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package any.functor.impl.pred;

import static any.functor.Functions.asBigDecimal;
import static any.functor.Functions.asBool;
import static any.functor.Functions.asDate;
import static any.functor.Functions.asDouble;
import static any.functor.Functions.asNumber;
import static any.functor.Functions.asObject;
import static any.functor.Functions.asPattern;
import static any.functor.Functions.asSerializable;
import static any.functor.Functions.asString;
import static any.functor.Functions.asUnknownObject;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import any.Any;
import any.ImmutableAny;
import any.functor.interfaces.FunctionTypedPredicate;
import any.functor.interfaces.NativePredicate;
import any.functor.interfaces.Predicate;
import any.functor.interfaces.PrimitiveFunctionTypedPredicate;
import any.impl.util.MapEntry;
import any.impl.util.NumberUtils;
import any.incubator.UnknownObject;

public class CoreAnyPredicates {

	private static final Predicate<Entry<String, ? extends ImmutableAny>> CONTAINS_KEYS = 
		new BasePredicateWithDescription<Entry<String, ? extends ImmutableAny>>("containsKeys") {
			public boolean apply(Entry<String, ? extends ImmutableAny> entry){
				return entry.getValue().containsKeys();
			}
		};

	private static final Predicate<Entry<String, ? extends ImmutableAny>> HAS_KEY = 
		new BasePredicateWithDescription<Entry<String,? extends ImmutableAny>>("hasKey") {
			public boolean apply(Entry<String, ? extends ImmutableAny> entry) {
				return (entry.getKey() != null);
			}
		};
	
	private static final Predicate<Entry<String, ? extends ImmutableAny>> IS_EMPTY = 
		new BasePredicateWithDescription<Entry<String, ? extends ImmutableAny>>("isEmpty") {
			public boolean apply(Entry<String, ? extends ImmutableAny> entry){
				return entry.getValue().isEmpty();
			}
		};
	
	private static final Predicate<Entry<String, ? extends ImmutableAny>> IS_SIMPLE_TYPE = 
		new BasePredicateWithDescription<Entry<String, ? extends ImmutableAny>>("isSimpleType") {
			public boolean apply(Entry<String, ? extends ImmutableAny> entry){
				return entry.getValue().isSimpleType();
			}
		};

	private static final PrimitiveFunctionTypedPredicate<Boolean, boolean[]> IS_BOOL_TYPE = 
		new IsTypePredicate<Boolean, boolean[]>(Boolean.class, "isBoolType", asBool());
	
	private static final FunctionTypedPredicate<Date> IS_DATE_TYPE = 
		new IsTypePredicate<Date, Void>(Date.class, "isDateType", asDate());

	private static final FunctionTypedPredicate<BigDecimal> IS_NUMBER_TYPE = 
		new IsTypePredicate<BigDecimal, Void>(Number.class, "isNumberType", asBigDecimal());

	private static final FunctionTypedPredicate<Object> IS_OBJECT_TYPE = 
		new IsTypePredicate<Object, Void>(Object.class, "isObjectType", asObject());
	
	private static final FunctionTypedPredicate<Serializable> IS_SERIALIZABLE_TYPE = 
		new IsTypePredicate<Serializable, Void>(Serializable.class, "isSerializableType", asSerializable());
	
	private static final FunctionTypedPredicate<String> IS_STRING_TYPE = 
		new IsTypePredicate<String, Void>(String.class, "isStringType", asString());
	
	private static final PrimitiveFunctionTypedPredicate<Double, double[]> IS_SPECIAL_NUMBER_TYPE = 
		new IsTypePredicate<Double, double[]>(Double.class, "isSpecialNumberType", asDouble());

	private static final FunctionTypedPredicate<UnknownObject> IS_UNKNOWN_OBJECT_TYPE = 
		new IsTypePredicate<UnknownObject, Void>(UnknownObject.class, "isUnknownObjectType", asUnknownObject());

		
	private static final PrimitiveFunctionTypedPredicate<Boolean, boolean[]> CAN_BE_BOOL = 
		new BaseFunctionTypedPredicate<Boolean, boolean[]>("canBeBool", asBool()) {
			public boolean apply(Entry<String, ? extends ImmutableAny> entry) {
				return (entry.getValue().asBool(null) != null);
			}
		};

	private static final FunctionTypedPredicate<Date> CAN_BE_DATE = 
		new BaseFunctionTypedPredicate<Date, Void>("canBeDate", asDate()) {
			public boolean apply(Entry<String, ? extends ImmutableAny> entry) {
				return (entry.getValue().asDate(null) != null);
			}
		};

	private static final FunctionTypedPredicate<Number> CAN_BE_NUMBER = 
		new BaseFunctionTypedPredicate<Number, Void>("canBeNumber", asNumber()) {
			public boolean apply(Entry<String, ? extends ImmutableAny> entry) {
				return (entry.getValue().give(asNumber().withDefault(null)) != null);
			}
		};

		private static final FunctionTypedPredicate<BigDecimal> CAN_BE_REGULAR_NUMBER = 
			new BaseFunctionTypedPredicate<BigDecimal, Void>("canBeRegularNumber", asBigDecimal()) {
			public boolean apply(Entry<String, ? extends ImmutableAny> entry) {
				return (entry.getValue().asBigDecimal(null) != null);
			}
		};
		
	private static final PrimitiveFunctionTypedPredicate<Double, double[]> CAN_BE_SPECIAL_NUMBER = 
		new BaseFunctionTypedPredicate<Double, double[]>("canBeSpecialNumber", asDouble()) {
			public boolean apply(Entry<String, ? extends ImmutableAny> entry) {
				return NumberUtils.isSpecialNumber(entry.getValue().asDouble(null));
			}
		};
	
	private static final FunctionTypedPredicate<Pattern> CAN_BE_PATTERN = 
		new BaseFunctionTypedPredicate<Pattern, Void>("canBePattern", asPattern()) {
			public boolean apply(Entry<String, ? extends ImmutableAny> entry) {
				return (entry.getValue().give(asPattern().withDefaultValue(null)) != null);
			}
		};

	private static final FunctionTypedPredicate<String> CAN_BE_STRING = 
		new BaseFunctionTypedPredicate<String, Void>("canBeString", asString()) {
			public boolean apply(Entry<String, ? extends ImmutableAny> entry) {
				return (entry.getValue().asString(null) != null);
			}
		};
	
	// ----------------------------------------------------------------------------------------
	
	@SuppressWarnings("unchecked")
	public static <T> Predicate<T> equalTo(final Object value) {
		if (value instanceof Predicate) {
			return (Predicate<T>)value;
		} else {
			return new AnyEqualTo(value);
		}
	}
	
	public static Predicate<Entry<String, ? extends ImmutableAny>> entryValueIs(final Object value) {
		return new BasePredicate<Entry<String,? extends ImmutableAny>>() {
			public boolean apply(Entry<String, ? extends ImmutableAny> entry) {
				return Any.util.equalAny(entry.getValue(), value);
			}
			@Override public String toString() {
				return "entryValueIs " + value;
			}
		};
	}
	
	public static Predicate<Entry<String, ? extends ImmutableAny>> entryIs(final String key, final Object value) {
		return new BasePredicate<Entry<String,? extends ImmutableAny>>() {
			public boolean apply(Entry<String, ? extends ImmutableAny> entry) {
				return MapEntry.createMapEntry(key, value).equals(entry);
			}
			@Override public String toString() {
				return "entryIs " + key + " => " + value;
			}
		};
	}
	
	public static Predicate<Entry<String, ? extends ImmutableAny>> allElements(Predicate<? super Entry<String, ? extends ImmutableAny>> predicate) throws NullPointerException {
		return new AllElements(predicate);
	}
	
	public static Predicate<Entry<String, ? extends ImmutableAny>> anyElement(Predicate<? super Entry<String, ? extends ImmutableAny>> predicate) throws NullPointerException {
		return new AnyElement(predicate);
	}
	
	public static Predicate<Entry<String, ? extends ImmutableAny>> containsKeys() {
		return CONTAINS_KEYS;
	}
	
	public static Predicate<Entry<String, ? extends ImmutableAny>> containsKey(String key) {
		return new ContainsKey(key);
	}
	
	public static Predicate<Entry<String, ? extends ImmutableAny>> hasKey() {
		return HAS_KEY;
	}

	public static Predicate<Entry<String, ? extends ImmutableAny>> hasKey(String key) {
		return new HasKey(key);
	}
	
	public static Predicate<Entry<String, ? extends ImmutableAny>> isEmpty() {
		return IS_EMPTY;
	}

	public static Predicate<Entry<String, ? extends ImmutableAny>> isSimpleType() {
		return IS_SIMPLE_TYPE;
	}
	
	public static PrimitiveFunctionTypedPredicate<Boolean, boolean[]> isBoolType() {
		return IS_BOOL_TYPE;
	}

	public static FunctionTypedPredicate<Date> isDateType() {
		return IS_DATE_TYPE;
	}

	/**
	 * Creates a predicate, which returns <tt>true</tt> if 
	 * the specified Any can be represented as a "regular" number (i.e. <b>not</b> <tt>NaN</tt> and 
	 * <b>not</b> positive/negative infinity).
	 * <p>
	 * <tt>isNumberType()</tt> and {@link #isSpecialNumberType()}
	 * are mutually exclusive: If one returns <tt>true</tt>,
	 * then the other will always return <tt>false</tt>.
	 * 
	 * @return <tt>true</tt> if this is a simple-type Any that represents a "regular" number.
	 * @see #isSpecialNumberType()
	 */
	public static FunctionTypedPredicate<BigDecimal> isNumberType() {
		return IS_NUMBER_TYPE;
	}

	public static PrimitiveFunctionTypedPredicate<Double, double[]> isSpecialNumberType() {
		return IS_SPECIAL_NUMBER_TYPE;
	}
	
	public static FunctionTypedPredicate<Object> isObjectType() {
		return IS_OBJECT_TYPE;
	}

	public static FunctionTypedPredicate<Serializable> isSerializableType() {
		return IS_SERIALIZABLE_TYPE;
	}

	public static FunctionTypedPredicate<String> isStringType() {
		return IS_STRING_TYPE;
	}

	public static FunctionTypedPredicate<UnknownObject> isUnknownObjectType() {
		return IS_UNKNOWN_OBJECT_TYPE;
	}
	
	// ----------------------------------------------------------------------------------------

	public static PrimitiveFunctionTypedPredicate<Boolean, boolean[]> canBeBool() {
		return CAN_BE_BOOL;
	}

	public static FunctionTypedPredicate<Date> canBeDate() {
		return CAN_BE_DATE;
	}

	//TODO: Zu canBeEnum() unbenennen?
	public static <E extends Enum<E>> FunctionTypedPredicate<E> isEnum(Class<E> cls) throws NullPointerException, IllegalArgumentException {
		return new IsEnum<E>(cls);
	}

	public static FunctionTypedPredicate<Number> canBeNumber() {
		return CAN_BE_NUMBER;
	}

	public static FunctionTypedPredicate<BigDecimal> canBeRegularNumber() {
		return CAN_BE_REGULAR_NUMBER;
	}
	
	public static PrimitiveFunctionTypedPredicate<Double, double[]> canBeSpecialNumber() {
		return CAN_BE_SPECIAL_NUMBER;
	}
	
	public static FunctionTypedPredicate<Pattern> canBePattern() {
		return CAN_BE_PATTERN;
	}

	public static FunctionTypedPredicate<String> canBeString() {
		return CAN_BE_STRING;
	}

	// ----------------------------------------------------------------------------------------

	public static NativePredicate isNative() {
		return new NativePredicate();
    }

}
